package com.netty.demo.server.netty.udp;

import com.netty.demo.server.netty.config.NettyConfig;
import com.netty.demo.server.netty.model.NettyNetwork;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.NetUtil;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * netty服务类
 *
 * @author yxl 余秀良
 * @version 1.0
 * <p>date: 2019年09月07日  下午4:30 </p>
 * <p>address: 四川成都 </p>
 */
@Component
@Slf4j
public class NettyUdpServer {
    @Resource
    private NettyUdpServerInitializer nettyServerInitializer;
    /**
     * netty服务类
     */
    private final Bootstrap bootstrap = new Bootstrap();
    /**
     * 申明线程组
     */
    private final NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();
    public Map<Integer, ChannelFuture> channelFutureMap = null;

    /**
     * netty服务启动类
     *
     * @author 余秀良
     * @version 1.0
     * <p>  date: 2019 - 09 - 07  16:32    </p>
     * <p>  address: 四川成都  </p>
     */
    public void start() {
        try {
            //配置线程组
            bootstrap.group(nioEventLoopGroup);
            //指定数据包通道为udp类型
            bootstrap.channel(NioDatagramChannel.class);
            //开启广播
            bootstrap.option(ChannelOption.SO_BROADCAST, true);
            //重复地址
            bootstrap.option(ChannelOption.SO_REUSEADDR, true);
            /*
             * TODO 如果发送接收超长数据 则需要放开该代码  并且设置最大长度
             * int max = xxx;
             * bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(max));
             * 标识当服务器请求处理线程全满时，用于临时存放已完成三次握手的请求的队列的最大长度
             * bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
             * 是否启用心跳保活机机制
             * bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
             */
            //设置通道初始化服务
            bootstrap.handler(nettyServerInitializer);
            if (channelFutureMap == null) {
                channelFutureMap = new LinkedHashMap<>(16);
            }
            bindIpAndPort(bootstrap);
        } catch (Exception e) {
            log.error("netty服务启动异常{}{}", e.getLocalizedMessage(), e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 绑定服务的ip和端口
     *
     * @param bootstrap Bootstrap
     */
    private void bindIpAndPort(Bootstrap bootstrap) {
        List<NettyNetwork> nettyNetworkList = NettyConfig.getServerConfig();
        if (nettyNetworkList != null && nettyNetworkList.size() > 0) {
            for (NettyNetwork nettyNetwork : nettyNetworkList) {
                int port = Integer.parseInt(nettyNetwork.getMonitorPort());
                NetworkInterface networkInterface = NetUtil.LOOPBACK_IF;
                Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
                InetAddress localAddress = null;
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    if (address instanceof Inet4Address) {
                        localAddress = address;
                    }
                }
                bootstrap.localAddress(localAddress, port);
                //组播
                bootstrap.option(ChannelOption.IP_MULTICAST_IF, networkInterface);
                bootstrap.option(ChannelOption.IP_MULTICAST_ADDR, localAddress);
                //设置监听端口 开启监听
                ChannelFuture channelFuture = bootstrap.bind(port);
                addListener(port, nettyNetwork.getMonitorIp(), channelFuture, networkInterface);
            }
        }
    }

    /**
     * @param port          端口号
     * @param ip            ip地址
     * @param channelFuture ChannelFuture
     */
    private void addListener(int port, String ip, ChannelFuture channelFuture, NetworkInterface networkInterface) {
        channelFuture.addListener(future1 -> {
            if (future1 != null && future1.isSuccess()) {
                NioDatagramChannel channel = (NioDatagramChannel) channelFuture.channel();
                InetSocketAddress multicastAddress = new InetSocketAddress(ip, port);
                channel.joinGroup(multicastAddress, networkInterface);
                channelFutureMap.put(port, channelFuture);
                log.info("netty服务启动成功,ip端口: {} {}", ip, port);
            } else {
                log.error("netty服务启动失败,ip端口: {} {}", ip, port);
                System.err.println("netty udp 服务启动失败,ip: " + ip + "端口:  " + port);
                assert future1 != null;
                future1.cause().printStackTrace();
            }
        });
    }


    public void stopAll() {
        nioEventLoopGroup.shutdownGracefully();
    }

    /**
     * 停止udp server服务
     * 销毁前的拦截
     */
    @PreDestroy
    public void destroy() {
        try {
            channelFutureMap.forEach((port, channelFuture) -> {
                Channel channel = channelFuture.channel();
                if (channel != null) {
                    try {
                        ChannelFuture channelFuture1 = channel.close().await();
                        if (!channelFuture1.isSuccess()) {
                            log.error("netty 通道关闭失败{}{}", channelFuture1.cause().getLocalizedMessage(), channelFuture1.cause().getMessage());
                        }
                    } catch (InterruptedException e) {
                        log.error("netty 通道关闭失败{}{}", e.getLocalizedMessage(), e.getMessage());
                        e.printStackTrace();
                    }
                }
            });
            Future<?> future = nioEventLoopGroup.shutdownGracefully().await();
            if (!future.isSuccess()) {
                log.error("netty线程组关闭失败,{}{}", future.cause().getLocalizedMessage(), future.cause().getMessage());
            }
        } catch (InterruptedException e) {
            log.error("netty服务关闭异常{}{}", e.getLocalizedMessage(), e.getMessage());
            e.printStackTrace();
        }
    }
}
